import logging
log = logging.getLogger(__name__)

__all__ = ['Stop', 'stop', 'State', 'StateManager', 'IterStateManager']

class Stop(Exception):
    """ Exception raised to stop an statemanager """
    pass


def stop():
    """ utility function, raises Stop() """
    raise Stop()


class State(object):
    """ Base State object to be used with StateManager """

    def on_enter(self):
        """ called whenever this state is made active """
        pass

    def on_exit(self):
        """ called whenever this state is made inactive """
        pass


class StateManager(object):
    """ State Manager class. Contains a stack of states and a current 
    state as the attributes state and states. 
    """

    def __init__(self):
        self.clear()

    def clear(self):
        self.state = None
        self.state_stack = []

    def replace_current_state(self, state):
        """ replaces the current state with a new state, the left of the 
        stack is left inplace 
        """
        log.debug("replaceCurrentState %s %s %s" % (state, self.state, 
                self.state_stack))
        if self.state is not None:
            self.state.on_exit()
        self.state = state
        state.on_enter()

    def push_state(self, state):
        """ sets a new state, and previous state is added to the state stack
        """
        log.debug("pushState %s %s %s" % (state, self.state, self.state_stack))
        if self.state is not None:
            self.state.on_exit()
            self.state_stack.append(self.state)
        self.state = state
        state.on_enter()

    def push_many(self, states):
        """ adds many states to the stack, the last state is popped
        """
        log.debug("push_many %s %s %s" % (states, self.state, self.state_stack))
        self.state_stack.extend(states)
        self.pop_state()

    def pop_state(self):
        """ removes the current state and tries to pop a new one from the
        stack
        """
        log.debug("pop_state %s %s" % (self.state, self.state_stack))
        if self.state:
            self.state.on_exit()

        try:
            self.state = self.state_stack.pop()
        except IndexError:
            stop()

        self.state.on_enter()


class IterStateManager(object):
    """ State manager that reads states from anything with a .next 
    method. If the object passed does not have a .next method iter() 
    is called on the object. 

    This manager *only* supports the popState method to select the 
    next state. You cannot push states onto this.

    You *must* call popState for the initial state to be set.
    """
    def __init__(self, states):
        if not hasattr(states, 'next'):
            self.states = iter(states)
        else:
            self.states = states
        self.state = None

    def pop_state(self):
        log.debug("pop_state %s %s" % (self.state, self.states))
        if self.state:
            self.state.on_exit()
        try:
            self.state = self.states.next()   
        except StopIteration:
            stop()
        self.state.on_enter()




